using EFCoreEnterpriseDemo.Models;
using EFCoreEnterpriseDemo.Services;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.ChangeTracking;
using Microsoft.EntityFrameworkCore.Metadata;
using System.Linq.Expressions;

namespace EFCoreEnterpriseDemo.Data
{
    public class ApplicationDbContext : DbContext
    {
        private readonly IHttpContextAccessor _httpContextAccessor;
        private readonly IDataPermissionService _permissionService;
        private readonly IAuditService _auditService;

        public ApplicationDbContext(
            DbContextOptions<ApplicationDbContext> options,
            IHttpContextAccessor httpContextAccessor,
            IDataPermissionService permissionService,
            IAuditService auditService) : base(options)
        {
            _httpContextAccessor = httpContextAccessor;
            _permissionService = permissionService;
            _auditService = auditService;
        }

        public DbSet<User> Users { get; set; }
        public DbSet<Department> Departments { get; set; }
        public DbSet<Order> Orders { get; set; }
        public DbSet<OrderItem> OrderItems { get; set; }
        public DbSet<AuditLog> AuditLogs { get; set; }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            // 软删除过滤器
            foreach (var entityType in modelBuilder.Model.GetEntityTypes()
                .Where(t => typeof(ISoftDelete).IsAssignableFrom(t.ClrType)))
            {
                AddSoftDeleteFilter(modelBuilder, entityType);
            }

            // 数据权限过滤器
            foreach (var entityType in modelBuilder.Model.GetEntityTypes()
                .Where(t => typeof(IDataPermission).IsAssignableFrom(t.ClrType)))
            {
                AddDataPermissionFilter(modelBuilder, entityType);
            }

            // 配置实体关系
            modelBuilder.Entity<User>()
                .HasOne(u => u.Department)
                .WithMany()
                .HasForeignKey(u => u.DepartmentId);

            modelBuilder.Entity<Order>()
                .HasOne(o => o.User)
                .WithMany()
                .HasForeignKey(o => o.UserId);

            modelBuilder.Entity<OrderItem>()
                .HasOne(oi => oi.Order)
                .WithMany(o => o.Items)
                .HasForeignKey(oi => oi.OrderId);

            // 索引配置
            modelBuilder.Entity<User>()
                .HasIndex(u => u.Email)
                .IsUnique();

            modelBuilder.Entity<Order>()
                .HasIndex(o => o.OrderNumber)
                .IsUnique();

            modelBuilder.Entity<AuditLog>()
                .HasIndex(a => new { a.TableName, a.RecordId });
        }

        public override async Task<int> SaveChangesAsync(CancellationToken cancellationToken = default)
        {
            var auditEntries = new List<AuditEntry>();

            foreach (var entry in ChangeTracker.Entries())
            {
                if (entry.Entity is AuditLog) continue;

                var auditEntry = CreateAuditEntry(entry);
                if (auditEntry != null) auditEntries.Add(auditEntry);

                SetAuditFields(entry);
            }

            var result = await base.SaveChangesAsync(cancellationToken);

            if (auditEntries.Any())
                await _auditService.SaveAuditLogsAsync(auditEntries);

            return result;
        }

        private void AddSoftDeleteFilter(ModelBuilder modelBuilder, IMutableEntityType entityType)
        {
            var parameter = Expression.Parameter(entityType.ClrType, "e");
            var body = Expression.Equal(
                Expression.Property(parameter, nameof(ISoftDelete.IsDeleted)),
                Expression.Constant(false));
            var lambda = Expression.Lambda(body, parameter);

            modelBuilder.Entity(entityType.ClrType).HasQueryFilter(lambda);
        }

        private void AddDataPermissionFilter(ModelBuilder modelBuilder, IMutableEntityType entityType)
        {
            var parameter = Expression.Parameter(entityType.ClrType, "e");
            Expression? body = null;

            var tenantId = _permissionService.GetCurrentTenantId();
            if (tenantId.HasValue)
            {
                body = Expression.Equal(
                    Expression.Property(parameter, nameof(IDataPermission.TenantId)),
                    Expression.Constant(tenantId));
            }

            var allowedDepartments = _permissionService.GetAllowedDepartments();
            if (allowedDepartments != null)
            {
                var deptCondition = Expression.Call(
                    typeof(Enumerable), // 静态方法所在类型
                    "Contains", // 方法名
                    new[] { typeof(int?) }, // 泛型类型参数
                    Expression.Convert(
                        Expression.Constant(allowedDepartments),
                        typeof(IEnumerable<int?>)
                    ),
                    Expression.Convert(
                        Expression.Property(parameter, nameof(IDataPermission.DepartmentId)),
                        typeof(int?)
                    )   
                );


                body = body == null ? deptCondition : Expression.AndAlso(body, deptCondition);
            }

            if (body != null)
            {
                var lambda = Expression.Lambda(body, parameter);
                modelBuilder.Entity(entityType.ClrType).HasQueryFilter(lambda);
            }
        }

        private AuditEntry? CreateAuditEntry(EntityEntry entry)
        {
            if (entry.State == EntityState.Detached || entry.State == EntityState.Unchanged)
                return null;

            var auditEntry = new AuditEntry
            {
                TableName = entry.Metadata.GetTableName(),
                Action = entry.State switch
                {
                    EntityState.Added => "Create",
                    EntityState.Modified => "Update",
                    EntityState.Deleted => "Delete",
                    _ => "Unknown"
                },
                RecordId = (int?)entry.Property("Id").CurrentValue,
                OldValues = entry.State == EntityState.Modified ? GetChangedValues(entry, false) : null,
                NewValues = GetChangedValues(entry, true),
                ChangedColumns = entry.State == EntityState.Modified ?
                    string.Join(",", entry.Properties.Where(p => p.IsModified).Select(p => p.Metadata.Name)) : null,
                UserId = _httpContextAccessor.HttpContext?.User?.FindFirst("sub")?.Value,
                UserName = _httpContextAccessor.HttpContext?.User?.Identity?.Name,
                IpAddress = _httpContextAccessor.HttpContext?.Connection?.RemoteIpAddress?.ToString(),
                UserAgent = _httpContextAccessor.HttpContext?.Request?.Headers["User-Agent"].ToString()
            };

            return auditEntry;
        }

        private string GetChangedValues(EntityEntry entry, bool isNew)
        {
            var values = new Dictionary<string, object?>();
            var properties = isNew ? entry.Properties : entry.Properties.Where(p => p.IsModified);

            foreach (var property in properties)
            {
                values[property.Metadata.Name] = isNew ?
                    property.CurrentValue : property.OriginalValue;
            }

            return System.Text.Json.JsonSerializer.Serialize(values);
        }

        private void SetAuditFields(EntityEntry entry)
        {
            if (entry.Entity is not BaseEntity entity) return;

            var userId = _httpContextAccessor.HttpContext?.User?.FindFirst("sub")?.Value;
            var now = DateTime.UtcNow;

            switch (entry.State)
            {
                case EntityState.Added:
                    entity.CreatedAt = now;
                    entity.CreatedBy = userId;
                    entity.TenantId = _permissionService.GetCurrentTenantId();
                    entity.DepartmentId = _permissionService.GetCurrentDepartmentId();
                    break;

                case EntityState.Modified:
                    entity.UpdatedAt = now;
                    entity.UpdatedBy = userId;
                    break;

                case EntityState.Deleted:
                    if (entity is ISoftDelete softDelete)
                    {
                        softDelete.IsDeleted = true;
                        softDelete.DeletedAt = now;
                        softDelete.DeletedBy = userId;
                        entry.State = EntityState.Modified;
                    }
                    break;
            }
        }
    }

    public class AuditEntry
    {
        public string? TableName { get; set; }
        public string? Action { get; set; }
        public int? RecordId { get; set; }
        public string? OldValues { get; set; }
        public string? NewValues { get; set; }
        public string? ChangedColumns { get; set; }
        public string? UserId { get; set; }
        public string? UserName { get; set; }
        public string? IpAddress { get; set; }
        public string? UserAgent { get; set; }
    }
}